#include <pthread.h>
#include <stdlib.h>

#include "base/simapi.h"

static pthread_rwlock_t lock;
static pthread_mutex_t trylock = PTHREAD_MUTEX_INITIALIZER;

void* run1(void* arglist)
{
    Sim_Print0("[run1]started\n");

    pthread_rwlock_rdlock(&lock);
    Sim_Print0("[run1]a read lock is obtained\n");

    pthread_rwlock_unlock(&lock);
    Sim_Print0("[main]a read lock is released\n");

    return NULL;
}


void* run2(void* arglist)
{
    Sim_Print0("[run2]started\n");

    int res = pthread_mutex_trylock(&trylock);
    Sim_Print1("[run2] try lock result %d\n", res);

    if (res == 0) {
        pthread_mutex_unlock(&trylock);
    }
    return NULL;
}

void mainX(int argc, const char** const argv, const char **envp) {
    // pretest
    if (Sim_GetNumCpus() < 2) {
        Sim_Print0("\nthis test requires at least 2 cpus\n");
        exit(0);
    }

    pthread_t pth;
    pthread_attr_t attr;
    int arg;

    pthread_attr_init(&attr);
    pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);

    pthread_rwlock_init(&lock, NULL);
    Sim_Print0("[main]a rwlock is initialized\n");

    // test 1 : read lock 
    Sim_Print0("\n1. read lock test\n");

    pthread_rwlock_rdlock(&lock);
    Sim_Print0("[main]a read lock is obtained\n");

    pthread_create(&pth, &attr, run1, &arg);
    Sim_Print0("[main]thread created with run1\n");

    pthread_join(pth, NULL);
    Sim_Print0("[main]thread joined\n");

    pthread_rwlock_unlock(&lock);
    Sim_Print0("[main]a read lock is released\n");

    // test 2 : write lock 
    Sim_Print0("\n2. write lock test\n");

    pthread_rwlock_wrlock(&lock);
    Sim_Print0("[main]a write lock is obtained\n");

    pthread_create(&pth, &attr, run1, &arg);
    Sim_Print0("[main]thread created with run1\n");

    int i;
    for (i = 0; i < 10; i++) {
        Sim_Print1("[main]idling %d\n", i);
    }

    pthread_rwlock_unlock(&lock);
    Sim_Print0("[main]a write lock is released\n");

    pthread_rwlock_destroy(&lock);

    pthread_join(pth, NULL);
    Sim_Print0("[main]thread joined\n");

    // test 3 : try lock 
    Sim_Print0("\n3. try lock test\n");

    // 3.1 trylock will be tried to an occupied lock
    pthread_mutex_lock(&trylock);
    Sim_Print0("[main]a lock is obtained\n");

    pthread_create(&pth, &attr, run2, &arg);
    Sim_Print0("[main]thread created with run2\n");

    pthread_join(pth, NULL);
    Sim_Print0("[main]thread joined\n");

    pthread_mutex_unlock(&trylock);
    Sim_Print0("[main]a lock is released\n");

    // 3.2 trylock will be tried to a free lock
    pthread_create(&pth, &attr, run2, &arg);
    Sim_Print0("[main]thread created with run2\n");

    pthread_join(pth, NULL);
    Sim_Print0("[main]thread joined\n");
}
